﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Dokan;
using WebDav.Client;
using WebDav.Client.Exceptions;

namespace WDTunnel
{
    
    class Program
    {
        private static string _url = "https://web-dav-drive.googlecode.com/svn/trunk/";
        private static string _username = "driver.edro";
        private static string _password = "Vf6Gj9US4NN5";





        static void Main(string[] args)
        {
            // pass prompt
            if (_password == String.Empty)
            {
                Console.WriteLine("Enter password for {0} at {1}: ", _username, _url);
                _password = Console.ReadLine();
            }
            Console.WriteLine("Logging in ...");

            // auth
            var session = new WebDavSession();
            session.Credentials = new NetworkCredential(_username, _password);
            session.RootUrl = _url;

            try
            {
                Console.WriteLine(session.CheckOptions(_url));
                Console.WriteLine("Login success.");
            }
            catch (UnauthorizedException)
            {
                Console.WriteLine("Login or password incorrect.");
                Console.ReadKey();
                return;
            }
            catch (Exception e)
            {
                Console.WriteLine("Unknown error: {0}",e.Message);
                Console.ReadKey();
                return;
            }




            // virtual drive
            var opt = new DokanOptions();
            opt.MountPoint = "r:\\";
            opt.DebugMode = true;
            opt.UseStdErr = true;
            opt.VolumeLabel = "WebDAV";
            opt.NetworkDrive = false;

            var tunnel = new WebdavFs(session);
            int status = DokanNet.DokanMain(opt, tunnel);
            switch (status)
            {
                case DokanNet.DOKAN_DRIVE_LETTER_ERROR:
                    Console.WriteLine("Drvie letter error");
                    break;
                case DokanNet.DOKAN_DRIVER_INSTALL_ERROR:
                    Console.WriteLine("Driver install error");
                    break;
                case DokanNet.DOKAN_MOUNT_ERROR:
                    Console.WriteLine("Mount error");
                    break;
                case DokanNet.DOKAN_START_ERROR:
                    Console.WriteLine("Start error");
                    break;
                case DokanNet.DOKAN_ERROR:
                    Console.WriteLine("Unknown error");
                    break;
                case DokanNet.DOKAN_SUCCESS:
                    Console.WriteLine("Drive create success");
                    break;
                default:
                    Console.WriteLine("Unknown status: %d", status);
                    break;
            }

            Console.ReadKey();

            DokanNet.DokanUnmount('r');

        }
    }

    internal class WebdavFs : DokanOperations
    {
        private readonly WebDavSession _session;



        public WebdavFs(WebDavSession session)
        {
            _session = session;
        }






        public int GetFileInformation(string filename, FileInformation fileinfo, DokanFileInfo info)
        {
            Console.Write("GetFileInformation: " + filename);

            if (filename == "\\")
            {
                fileinfo.CreationTime = DateTime.Now;
                //fileinfo.FileName = filename;
                fileinfo.LastAccessTime = DateTime.Now;
                fileinfo.LastWriteTime = DateTime.Now;
                fileinfo.Attributes = FileAttributes.Directory;

                Console.WriteLine("... OK");
                return 0;
            }

            var unixFileName = filename.Replace("\\", "/");
            try
            {
                var resource = _session.OpenResourceRelative(unixFileName);

                if (resource != null)
                {
                    fileinfo.CreationTime = resource.CreationDate;
                    fileinfo.FileName = resource.DisplayName;
                    fileinfo.LastAccessTime = resource.LastModified;
                    fileinfo.LastWriteTime = resource.LastModified;
                    fileinfo.Attributes = resource.ItemType == ItemType.Folder
                        ? FileAttributes.Directory
                        : FileAttributes.Normal;

                    // len
                    var paramname = new[] {new PropertyName("getcontentlength", "DAV:")};
                    var property = resource.GetPropertyValues(paramname).FirstOrDefault();
                    fileinfo.Length = property != null ? Convert.ToInt64(property.StringValue) : 0;

                    Console.WriteLine("... OK");
                    return 0;
                }
                else
                {
                    Console.WriteLine("... ERROR_FILE_NOT_FOUND");
                    return -1 * DokanNet.ERROR_FILE_NOT_FOUND;
                }
            }
            catch (Exception e)
            {
               
                Console.WriteLine("... ERROR_FILE_NOT_FOUND");
                Console.WriteLine("GetFileInformation ({1}) error: {0}", e.Message, filename);
                return -1 * DokanNet.ERROR_FILE_NOT_FOUND;
            }

        }







        public int FindFiles(string filename, ArrayList files, DokanFileInfo info)
        {
            Console.WriteLine("List dir: " + filename);

            var winFileName = filename.Replace("\\", "/");

            try
            {
                var folder = _session.OpenFolderRelative(winFileName);

                foreach (var child in folder.GetChildren())
                {
                    var file = new FileInformation();
                    file.CreationTime = child.CreationDate;
                    file.FileName = child.DisplayName;
                    file.LastAccessTime = child.LastModified;
                    file.LastWriteTime = child.LastModified;
                    file.Attributes = child.ItemType == ItemType.Folder
                        ? FileAttributes.Directory
                        : FileAttributes.Normal;

                    var paramname = new[] {new PropertyName("getcontentlength", "DAV:")};
                    var property = child.GetPropertyValues(paramname).FirstOrDefault();
                    if (property != null) file.Length = Convert.ToInt64(property.StringValue);

                    files.Add(file);
                }

            }
            catch (Exception e)
            {
                Console.WriteLine("FindFiles error: {0}", e.Message);
            }

          
            return 0;
        }



        // empty
        public int GetDiskFreeSpace(
            ref ulong freeBytesAvailable, 
            ref ulong totalBytes, 
            ref ulong totalFreeBytes, 
            DokanFileInfo info)
        {
            Console.WriteLine("GetDiskFreeSpace");

            freeBytesAvailable = 1024L * 1024 * 1024 * 100;
            totalBytes = 1024L * 1024 * 1024 * 100;
            totalFreeBytes = 1024L * 1024 * 1024 * 100;
            return 0;
        }





        public int WriteFile(string filename, byte[] buffer, ref uint writtenBytes, long offset, DokanFileInfo info)
        {
            Console.WriteLine("WriteFile({0}, {1}) ", filename, offset);

            var winFileName = filename.Replace("\\", "/");

            try
            {
                var resource = _session.OpenResourceRelative(winFileName);

                if (resource.ItemType == ItemType.Folder)
                    return -1 * DokanNet.ERROR_FILE_NOT_FOUND;

                var ws = resource.GetWriteStream(buffer.Length + offset);
                //ws.Seek(offset, SeekOrigin.Begin);
                ws.Write(buffer, Convert.ToInt32(offset), buffer.Length);

                writtenBytes = Convert.ToUInt32(buffer.Length);

                Console.WriteLine(" ... OK");
                return 0;
            }
            catch (Exception e)
            {
                Console.WriteLine("WriteFile error: {0} {1}", e.Message, e.InnerException);

                return -1 * DokanNet.ERROR_FILE_NOT_FOUND;
            }

        }




        public int Cleanup(string filename, DokanFileInfo info)
        {
            // Console.WriteLine("Cleanup: " + filename);
            return 0;
        }




        public int CloseFile(string filename, DokanFileInfo info)
        {
            // Console.WriteLine("CloseFile: " + filename);
            return 0;
        }




        public int CreateDirectory(string filename, DokanFileInfo info)
        {
            Console.WriteLine("CreateDirectory: " + filename);
            return -1;
        }




        public int CreateFile(
            string filename,
            System.IO.FileAccess access,
            System.IO.FileShare share,
            System.IO.FileMode mode,
            System.IO.FileOptions options,
            DokanFileInfo info)
        {
            // Console.WriteLine("CreateFile: " + filename);
            return 0;
        }

        public int DeleteDirectory(string filename, DokanFileInfo info)
        {
            Console.WriteLine("DeleteDirectory: " + filename);
            return -1;
        }

        public int DeleteFile(string filename, DokanFileInfo info)
        {
            Console.WriteLine("DeleteFile: " + filename);
            return -1;
        }


        

        public int FlushFileBuffers(
           string filename,
           DokanFileInfo info)
        {
            Console.WriteLine("FlushFileBuffers: " + filename);
            return -1;
        }

       



        public int LockFile(
            string filename,
            long offset,
            long length,
            DokanFileInfo info)
        {
            Console.WriteLine("LockFile: " + filename);
            return 0;
        }






        public int MoveFile(
            string filename,
            string newname,
            bool replace,
            DokanFileInfo info)
        {
            Console.WriteLine("MoveFile: " + filename);
            return -1;
        }





        public int OpenDirectory(string filename, DokanFileInfo info)
        {
            //Console.WriteLine("OpenDirectory: " + filename);
            return 0;
        }







        public int ReadFile(
            string filename,
            byte[] buffer,
            ref uint readBytes,
            long offset,
            DokanFileInfo info)
        {
            Console.Write("ReadFile: {2} (l:{0} o:{1} s:{3})", readBytes, offset, filename, buffer.Length);

            // 
            var winFileName = filename.Replace("\\", "/");
            
            try
            {
                var resource = _session.OpenResourceRelative(winFileName);

                if (resource.ItemType == ItemType.Folder)
                    return -1 * DokanNet.ERROR_FILE_NOT_FOUND;
  
                var data = resource.DownloadBytes();
                var lengthToCopy = data.Length - offset;
                if (lengthToCopy > buffer.Length) lengthToCopy = buffer.Length;
                Array.Copy(data, offset, buffer, 0, lengthToCopy);
                readBytes = Convert.ToUInt32(lengthToCopy);

                Console.WriteLine(" ... OK");
                return 0;
            }
            catch (Exception e)
            {
                Console.WriteLine("ReadFile error: {0} {1}", e.Message, e.InnerException );

                return -1 * DokanNet.ERROR_FILE_NOT_FOUND;
            }
            
        }




        public int SetEndOfFile(string filename, long length, DokanFileInfo info)
        {
            Console.WriteLine("SetEndOfFile: " + filename);
            return -1;
        }







        public int SetAllocationSize(string filename, long length, DokanFileInfo info)
        {
            Console.WriteLine("SetAllocationSize: " + filename);
            return -1;
        }





        public int SetFileAttributes(
            string filename,
            System.IO.FileAttributes attr,
            DokanFileInfo info)
        {
            Console.WriteLine("SetFileAttributes: " + filename);
            return -1;
        }





        public int SetFileTime(
            string filename,
            DateTime ctime,
            DateTime atime,
            DateTime mtime,
            DokanFileInfo info)
        {
            Console.WriteLine("SetFileTime: " + filename);
            return -1;
        }





        public int UnlockFile(string filename, long offset, long length, DokanFileInfo info)
        {
            Console.WriteLine("UnlockFile: " + filename);
            return 0;
        }

        

        public int Unmount(DokanFileInfo info)
        {
            Console.WriteLine("Unmount");
            return 0;
        }



    }
}
